package cn.demoncat.util.lang;

import cn.demoncat.util.exception.ParamRuntimeException;
import cn.demoncat.util.lang.constant.StringConstant;
import cn.demoncat.util.web.IpUtil;
import org.apache.commons.lang3.StringUtils;

import java.time.LocalDateTime;

/**
 * Twitter Snowflake 分布式ID生成器：雪花算法
 * 
 * <pre>
 * 结构：
 * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 0000000000 - 000000000000
 * 正数标识(固定为0表示正数) - 时间戳(当前时间戳 - 开始时间戳) - 机器标识(2^5个workerId + 2^5个dataCenterId) - 自增序列(2^12)
 * 
 * 效果：
 * 1、时间戳可以保证 (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69 年内不重复
 * 2、通过指定workerId(0~31)和dataCenterId(0~31)，可以分布在1024个节点上
 * 3、每个机器每毫秒可以生成4096个自增序列
 * 4、组合结果为64位的Long值，18-20位
 * 
 * 总结：
 * 区分度：时间戳差 + 1024个节点 + 4096个序列
 * 问题：时间戳差只能使用69年
 *
 * 注意：
 * 前端JS最多能处理16位数值，所以应当保存为字符串
 * </pre>
 * 
 * @author 延晓磊
 *
 * @since 2019年9月4日
 */
public class SnowflakeId {

	// 开始时间戳：当前时间戳 - 69年 < 开始时间戳 < 当前时间戳
	private static final long START_TIME = LocalDateUtil.getTime(LocalDateTime.of(2020, 1, 1, 0, 0, 0));
	// 时间戳差
	private static final long MAX_TIME_DIFF = 69L * 365 * 24 * 60 * 60 * 1000;

	/**
	 * IP SnowflakeId 实例
	 */
	public static final SnowflakeId IP_INSTANCE = getIpSnowflake();
	
	
	/**
	 * 工作机器ID(0~31)
	 */
	private final long workerId;

	/**
	 * 数据中心ID(0~31)
	 */
	private final long dataCenterId;

	/**
	 * 毫秒内序列(0~4095)
	 */
	private long sequence = 0L;

	/**
	 * 上次生成ID的时间戳
	 */
	private long lastTime = -1L;

	/**
	 * Snowflake ID生成器
	 *
	 * @param workerId     工作机器ID (0~31)
	 * @param dataCenterId 数据中心ID (0~31)
	 */
	public SnowflakeId(long workerId, long dataCenterId) {
		if (workerId > 31 || workerId < 0 || dataCenterId > 31 || dataCenterId < 0) {
			throw new ParamRuntimeException("生成SnowflakeId失败：节点ID必须是 0~31 之间的整数");
		}
		if (System.currentTimeMillis() - START_TIME > MAX_TIME_DIFF) {
			throw new ParamRuntimeException("生成SnowflakeId失败：时间差不能大于69年");
		}
		this.workerId = workerId;
		this.dataCenterId = dataCenterId;
	}

	/**
	 * 根据IP创建SnowflakeId 实例
	 * 
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年9月4日
	 */
	public static SnowflakeId getIpSnowflake() {
		// 根据IP获取workerId
		String ip = IpUtil.LOCAL_IP;
		int[] ints = StringUtils.toCodePoints(ip);
        int sums = 0;
        for (int i: ints) {
            sums += i;
        }
	    long workerId = sums % 32;
	    // 根据IP获取dataCenterId 
	    long dataCenterId = LongUtil.valueOfZero(ip.substring(ip.lastIndexOf(StringConstant.POINT) + 1)) % 32;
	    // 创建实例
	    return new SnowflakeId(workerId, dataCenterId);
	}

	/**
	 * 获得ID
	 * 
	 * @return 随机ID 18位
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年9月4日
	 */
	public synchronized long get() {
		// 当前时间戳
		long now = System.currentTimeMillis();

		// 如果当前时间小于上次生成ID的时间，说明服务器时间有问题
		if (now < lastTime) {
			throw new RuntimeException("生成SnowflakeId失败：服务器时间设置错误");
		}

		// 时间戳重复，自增序列
		if (now == lastTime) {
			sequence ++;
			// 超出4095
			if (sequence > 4095) {
				// 获得新的时间戳
				now = System.currentTimeMillis();
				while (now == lastTime) {
					now = System.currentTimeMillis();
				}
			}
		} else {
			// 时间戳增加，重置序列
			sequence = 0L;
		}

		// 更新上次生成ID的时间戳
		lastTime = now;

		// 组装64位Long值
		return ((now - START_TIME) << 22) | (workerId << 17) | (dataCenterId << 12) | sequence;
	}

	/**
	 * 根据默认的IP SnowflakeId 实例 获取ID
	 * 
	 * @return 随机ID 18位
	 * 
	 * @author 延晓磊
	 *
	 * @since 2019年9月4日
	 */
	public static long getByIp() {
		return IP_INSTANCE.get();
	}
	
}
